/*
 *  linux/fs/inode.c
 *
 *  (C) 1991  Linus Torvalds
 */

#include "../include/string.h"
#include "../include/sys/stat.h"

#include "../include/linux/sched.h"
#include "../include/asm/system.h"
//#include "../include/linux/mm.h"

extern int *blk_size[];

struct m_inode inode_table[NR_INODE] = {{0,},};

static void read_inode(struct m_inode *inode);

static void write_inode(struct m_inode *inode);

static inline void wait_on_inode(struct m_inode *inode) {
    cli();
    while (inode->i_lock)
        sleep_on(&inode->i_wait);
    sti();
}

static inline void lock_inode(struct m_inode *inode) {
    cli();
    while (inode->i_lock)
        sleep_on(&inode->i_wait);
    inode->i_lock = 1;
    sti();
}

static inline void unlock_inode(struct m_inode *inode) {
    inode->i_lock = 0;
    wake_up(&inode->i_wait);
}

void invalidate_inodes(int dev) {
    int i;
    struct m_inode *inode;

    inode = 0 + inode_table;
    for (i = 0; i < NR_INODE; i++, inode++) {
        wait_on_inode(inode);
        if (inode->i_dev == dev) {
            if (inode->i_count)
                printk("inode in use on removed disk\n\r");
            inode->i_dev = inode->i_dirt = 0;
        }
    }
}

void sync_inodes(void) {
    int i;
    struct m_inode *inode;

    inode = 0 + inode_table;
    for (i = 0; i < NR_INODE; i++, inode++) {
        wait_on_inode(inode);
        if (inode->i_dirt && !inode->i_pipe)
            write_inode(inode);
    }
}

static int _bmap(struct m_inode *inode, int block, int create) {
    struct buffer_head *bh;
    int i;

    if (block < 0)
        panic("_bmap: block<0");
    if (block >= 7 + 512 + 512 * 512)
        panic("_bmap: block>big");
    if (block < 7) {
        if (create && !inode->i_zone[block])
            if ((inode->i_zone[block] = new_block(inode->i_dev))) {
                inode->i_ctime = CURRENT_TIME;
                inode->i_dirt = 1;
            }
        return inode->i_zone[block];
    }
    block -= 7;
    if (block < 512) {
        if (create && !inode->i_zone[7])
            if ((inode->i_zone[7] = new_block(inode->i_dev))) {
                inode->i_dirt = 1;
                inode->i_ctime = CURRENT_TIME;
            }
        if (!inode->i_zone[7])
            return 0;
        if (!(bh = bread(inode->i_dev, inode->i_zone[7])))
            return 0;
        i = ((unsigned short *) (bh->b_data))[block];
        if (create && !i)
            if ((i = new_block(inode->i_dev))) {
                ((unsigned short *) (bh->b_data))[block] = i;
                bh->b_dirt = 1;
            }
        brelse(bh);
        return i;
    }
    block -= 512;
    if (create && !inode->i_zone[8])
        if ((inode->i_zone[8] = new_block(inode->i_dev))) {
            inode->i_dirt = 1;
            inode->i_ctime = CURRENT_TIME;
        }
    if (!inode->i_zone[8])
        return 0;
    if (!(bh = bread(inode->i_dev, inode->i_zone[8])))
        return 0;
    i = ((unsigned short *) bh->b_data)[block >> 9];
    if (create && !i)
        if ((i = new_block(inode->i_dev))) {
            ((unsigned short *) (bh->b_data))[block >> 9] = i;
            bh->b_dirt = 1;
        }
    brelse(bh);
    if (!i)
        return 0;
    if (!(bh = bread(inode->i_dev, i)))
        return 0;
    i = ((unsigned short *) bh->b_data)[block & 511];
    if (create && !i)
        if ((i = new_block(inode->i_dev))) {
            ((unsigned short *) (bh->b_data))[block & 511] = i;
            bh->b_dirt = 1;
        }
    brelse(bh);
    return i;
}

int bmap(struct m_inode *inode, int block) {
    return _bmap(inode, block, 0);
}

int create_block(struct m_inode *inode, int block) {
    return _bmap(inode, block, 1);
}

//// 放回（放置）一个i节点(回写入设备)。
void iput(struct m_inode *inode) {
    if (!inode)
        return;
    wait_on_inode(inode);
    if (!inode->i_count)
        panic("iput: trying to free free inode");

    // 如果是管道i节点
    if (inode->i_pipe) {
        // 唤醒等待该管道的进程，
        wake_up(&inode->i_wait);
        wake_up(&inode->i_wait2);
        // 引用次数减1，如果大于0，说明还有引用则返回。
        if (--inode->i_count)
            return;
        // 释放管道占用的内存页面，对于管道节点，inode->i_size 存放着内存页地址。参见 get_pipe_inode ()
        free_page(inode->i_size);
        // 复位该节点的引用计数值、已修改标志和管道标志
        inode->i_count = 0;
        inode->i_dirt = 0;
        inode->i_pipe = 0;
        return;
    }
    // 如果i节点对应的设备号=0，则将此节点的引用计数递减1，返回。
    // 例如用于管道操作的i节点，其i节点的设备号为0。
    if (!inode->i_dev) {
        inode->i_count--;
        return;
    }
    //如果是块设备文件的i节点，此时逻辑块字段0(i_zone[0]）中是设备号，
    // 则刷新该设备、等待 i节点解锁。
    if (S_ISBLK(inode->i_mode)) {
        sync_dev(inode->i_zone[0]);
        wait_on_inode(inode);
    }


    repeat:
    //如果i节点的引用计数大于1，则计数递减1后就直接返回（因为该i节点还有人在用，不能释放），
    // 否则就说明 i 节点的引用计数值为1（因为上面已经判浙过引用计数是否为零）。
    if (inode->i_count > 1) {
        inode->i_count--;
        return;
    }

    //如果i节点的链接数为0，则说明i节点对应文件被删除。于是释放该i节点的所有逻辑块，并释放该i节点。
    if (!inode->i_nlinks) {
        truncate(inode);
        // 用于实际释放i节点操作，即复位 i节点对应的i节点位图比特位，清空i节点结构内容。
        free_inode(inode);
        return;
    }
    // 如果该i节点己作过修改，则回写更新该i节点，并等待该i节点解锁。
    // 由于这里在写i节点时需要等待睡眠，此时其他进程有可能修改该i节点，
    // 因此在进程被唤醒后需要再次重复进行上述判断过程 (repeat)
    if (inode->i_dirt) {
        write_inode(inode);    /* we can sleep - so do again */
        wait_on_inode(inode);
        goto repeat;
    }

    // 程序若能执行到此，则说明该i节点的引用计数值 i_count 是1、链接数不为零，并且内容没有被修改过。
    // 因此此时只要把i节点引用计数递减1，返回。此时该 i节点的 i_count=0, 表示己释放。
    inode->i_count--;
    return;
}

struct m_inode *get_empty_inode(void) {
    struct m_inode *inode;
    static struct m_inode *last_inode = inode_table;
    int i;

    do {
        inode = NULL;
        for (i = NR_INODE; i; i--) {
            if (++last_inode >= inode_table + NR_INODE)
                last_inode = inode_table;
            if (!last_inode->i_count) {
                inode = last_inode;
                if (!inode->i_dirt && !inode->i_lock)
                    break;
            }
        }
        if (!inode) {
            for (i = 0; i < NR_INODE; i++)
                printk("%04x: %6d\t", inode_table[i].i_dev,
                       inode_table[i].i_num);
            panic("No free inodes in mem");
        }
        wait_on_inode(inode);
        while (inode->i_dirt) {
            write_inode(inode);
            wait_on_inode(inode);
        }
    } while (inode->i_count);
    memset(inode, 0, sizeof(*inode));
    inode->i_count = 1;
    return inode;
}

struct m_inode *get_pipe_inode(void) {
    struct m_inode *inode;

    if (!(inode = get_empty_inode()))
        return NULL;
    if (!(inode->i_size = get_free_page())) {
        inode->i_count = 0;
        return NULL;
    }
    inode->i_count = 2;    /* sum of readers/writers */
    PIPE_HEAD(*inode) = PIPE_TAIL(*inode) = 0;
    inode->i_pipe = 1;
    return inode;
}

//取得一个i节点。
//参数：dey-设备号；nr一i节点号。
// 从设备上读取指定节点号的i节点到内存i节点表中，并返回该i节点指针。
// 首先在位于高速缓冲区中的i节点表中搜寻，若找到指定节点号的i节点则在经过一些判断处理后返回该i节点指针。
// 否则从设备 dev 上读取指定i节点号的i节点信息放入i节点表中，并返回该i节点指针。
struct m_inode *iget(int dev, int nr) {
    struct m_inode *inode, *empty;

    //首先判断参数有效性。若设备号是0，则表明内核代码问题，显示出错信息并停机。
    if (!dev)
        panic("iget with dev==0");
    // 预先从i节点表中取一个空闲i节点备用。
    empty = get_empty_inode();
    //接着扫描i节点表。寻找参数指定节点号 nr的i节点。并递增该节点的引用次数。
    inode = inode_table;
    while (inode < NR_INODE + inode_table) {
        // 如果当前扫描i节点的设备号不等于指定的设备号或者节点号不等于指定的节点号，则继续扫描。
        if (inode->i_dev != dev || inode->i_num != nr) {
            inode++;
            continue;
        }
        //如果找到指定设备号 dev 和节点号 nr 的i节点，则等待该节点解锁（如果己上锁的话）
        wait_on_inode(inode);
        //在等待该节点解锁过程中，i节点表可能会发生变化。所以再次进行上述相同判断。如果发
        //生了变化，则再次重新扫描整个 i节点表。
        if (inode->i_dev != dev || inode->i_num != nr) {
            inode = inode_table;
            continue;
        }
        // 到这里表示找到相应的i节点。于是将该i节点引用计数增1。
        inode->i_count++;
        // 是否是另一个文件系统的安装点。若是则寻找被安装文件系统根节点并返回。
        if (inode->i_mount) {
            int i;

            // 该i节点的确是其他文件系统的安装点，则在超级块表中搜寻安装在此i节点的超级块。
            for (i = 0; i < NR_SUPER; i++)
                if (super_block[i].s_imount == inode)
                    break;
            // 如果没有找到，则显示出错信息，并放回本函数开始时获取的空闲节点 empty，返回该i节点指针。
            if (i >= NR_SUPER) {
                printk("Mounted inode hasn't got sb\n");
                if (empty)
                    iput(empty);
                return inode;
            }
            //执行到这里表示己经找到安装到 inode 节点的文件系统超级块。
            // 于是将该i节点写盘放回，并从安装在此i节点上的文件系统超级块中取设备号，并令i节点号为 ROOT_INO，即为1。
            iput(inode);
            dev = super_block[i].s_dev;
            nr = ROOT_INO;
            inode = inode_table;
            //然后重新扫描整个 i节点表，以获取该被安装文件系统的根i节点信息。
            continue;
        }
        if (empty)
            iput(empty);
        return inode;
    }
    //如果我们在i节点表中没有找到指定的i节点，
    // 则利用前面申请的空闲 i节点 empty, 在节点表中建立该i节点。
    if (!empty)
        return (NULL);
    inode = empty;
    inode->i_dev = dev;
    inode->i_num = nr;
    // 从相应设备上读取该i节点信息，返回该i节点指针。
    read_inode(inode);
    return inode;
}

////读取指定i节点信息。
// 从设备上读取含有指定i节点信息的i节点盘块，然后复制到指定的i节点结构中。
// 为了确定i节点所在的设备逻辑块号（或缓冲块），必须首先读取相应设备上的超级块，然后根据i节点号计算出i节点所在的逻辑块号。
// 在计算出i节点所在的逻辑块号后，就把该逻辑块读入一缓冲块中。然后把缓冲块中指定的i节点内容 复制到 inode。
static void read_inode(struct m_inode *inode) {
    struct super_block *sb;
    struct buffer_head *bh;
    int block;
    //锁定该i节点，
    lock_inode(inode);
    // 取该节点所在设备的超级块。
    if (!(sb = get_super(inode->i_dev)))
        panic("trying to read inode without dev");
    //该i节点所在的设备逻辑块号=（启动块+ 超级块）+ i 节点位图占用的块数 + 逻辑块位图占用的块数 +（i节点号-1)/每块含有的i节点数。
    // 虽然i节点号从0开始编号，但第1个0号i节点不用，并且磁盘上也不保存对应的0号i节点结构。
    // 因此存放i节点的盘块的 第1块上保存的是i节点号是1--16 的i节点结构而不是0--15的。
    // 因此在计算i节点号对应的i节点结构所在盘块时需要减1，即：B=(i节点号-1)/每块含有 i节点结构数。 例如，节点号16 的讠节点结构应该在 B=(16-1)/16=0 的块上。
    block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks + (inode->i_num - 1) / INODES_PER_BLOCK;
    // 从设备上读取该i节点所在的逻辑块，
    if (!(bh = bread(inode->i_dev, block)))
        panic("unable to read i-node block");
    // 复制指定 i节点内容到 inode 指针所指位置处。
    *(struct d_inode *) inode = ((struct d_inode *) bh->b_data)[(inode->i_num - 1) %
                                                                INODES_PER_BLOCK];
    brelse(bh);
    if (S_ISBLK(inode->i_mode)) {
        int i = inode->i_zone[0];
        if (blk_size[MAJOR(i)])
            inode->i_size = 1024*blk_size[MAJOR(i)][MINOR(i)];
        else
            inode->i_size = 0x7fffffff;
    }
    unlock_inode(inode);
}

static void write_inode(struct m_inode *inode) {
    struct super_block *sb;
    struct buffer_head *bh;
    int block;

    lock_inode(inode);
    if (!inode->i_dirt || !inode->i_dev) {
        unlock_inode(inode);
        return;
    }
    if (!(sb = get_super(inode->i_dev)))
        panic("trying to write inode without device");
    block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks + (inode->i_num - 1) / INODES_PER_BLOCK;
    if (!(bh = bread(inode->i_dev, block)))
        panic("unable to read i-node block");
    ((struct d_inode *) bh->b_data)[(inode->i_num - 1) %
                                    INODES_PER_BLOCK] = *(struct d_inode *) inode;
    bh->b_dirt = 1;
    inode->i_dirt = 0;
    brelse(bh);
    unlock_inode(inode);
}